package com.ruoyi.framework.web.service

import com.ruoyi.common.constant.CacheConstants
import com.ruoyi.common.constant.Constants
import com.ruoyi.common.constant.UserConstants
import com.ruoyi.common.core.domain.entity.SysUser
import com.ruoyi.common.core.domain.model.LoginUser
import com.ruoyi.common.core.redis.RedisCache
import com.ruoyi.common.exception.ServiceException
import com.ruoyi.common.exception.user.BlackListException
import com.ruoyi.common.exception.user.CaptchaException
import com.ruoyi.common.exception.user.CaptchaExpireException
import com.ruoyi.common.exception.user.UserNotExistsException
import com.ruoyi.common.exception.user.UserPasswordNotMatchException
import com.ruoyi.common.utils.DateUtils.nowDate
import com.ruoyi.common.utils.MessageUtils.message
import com.ruoyi.common.utils.MyStringUtils
import com.ruoyi.common.utils.ip.IpUtils.ipAddr
import com.ruoyi.common.utils.ip.IpUtils.isMatchedIp
import com.ruoyi.framework.manager.AsyncManager.Companion.me
import com.ruoyi.framework.manager.factory.AsyncFactory.recordLogininfor
import com.ruoyi.framework.security.context.AuthenticationContextHolder
import com.ruoyi.framework.security.context.AuthenticationContextHolder.clearContext
import com.ruoyi.system.service.ISysConfigService
import com.ruoyi.system.service.ISysUserService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.stereotype.Component
import javax.annotation.Resource

/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
class SysLoginService {
    @Autowired
    private lateinit var tokenService: TokenService

    @Resource
    private lateinit var authenticationManager: AuthenticationManager

    @Autowired
    private lateinit var redisCache: RedisCache

    @Autowired
    private lateinit var userService: ISysUserService

    @Autowired
    private lateinit var configService: ISysConfigService

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    fun login(username: String?, password: String?, code: String?, uuid: String?): String {
        // 验证码校验
        validateCaptcha(username, code, uuid)
        // 登录前置校验
        loginPreCheck(username, password)
        // 用户验证
        val authentication: Authentication
        try {
            val authenticationToken = UsernamePasswordAuthenticationToken(username, password)
            AuthenticationContextHolder.context = authenticationToken
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken)
        } catch (e: Exception) {
            if (e is BadCredentialsException) {
                me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("user.password.not.match")))
                throw UserPasswordNotMatchException()
            } else {
                me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, e.message))
                throw ServiceException(e.message)
            }
        } finally {
            clearContext()
        }
        me().execute(recordLogininfor(username, Constants.LOGIN_SUCCESS, message("user.login.success")))
        val loginUser = authentication.principal as LoginUser
        recordLoginInfo(loginUser.userId)
        // 生成token
        return tokenService.createToken(loginUser)
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    fun validateCaptcha(username: String?, code: String?, uuid: String?) {
        val captchaEnabled = configService.selectCaptchaEnabled()
        if (captchaEnabled) {
            val verifyKey = CacheConstants.CAPTCHA_CODE_KEY + MyStringUtils.nvl(uuid, "")
            val captcha = redisCache.getCacheObject<String>(verifyKey)
            redisCache.deleteObject(verifyKey)
            if (captcha == null) {
                me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("user.jcaptcha.expire")))
                throw CaptchaExpireException()
            }
            if (!code.equals(captcha, ignoreCase = true)) {
                me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("user.jcaptcha.error")))
                throw CaptchaException()
            }
        }
    }

    /**
     * 登录前置校验
     * @param username 用户名
     * @param password 用户密码
     */
    fun loginPreCheck(username: String?, password: String?) {
        // 用户名或密码为空 错误
        if (MyStringUtils.isEmpty(username) || MyStringUtils.isEmpty(password)) {
            me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("not.null")))
            throw UserNotExistsException()
        }
        // 密码如果不在指定范围内 错误
        if (password!!.length < UserConstants.PASSWORD_MIN_LENGTH
                || password.length > UserConstants.PASSWORD_MAX_LENGTH) {
            me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("user.password.not.match")))
            throw UserPasswordNotMatchException()
        }
        // 用户名不在指定范围内 错误
        if (username!!.length < UserConstants.USERNAME_MIN_LENGTH
                || username.length > UserConstants.USERNAME_MAX_LENGTH) {
            me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("user.password.not.match")))
            throw UserPasswordNotMatchException()
        }
        // IP黑名单校验
        val blackStr = configService.selectConfigByKey("sys.login.blackIPList")
        if (isMatchedIp(blackStr!!, ipAddr)) {
            me().execute(recordLogininfor(username, Constants.LOGIN_FAIL, message("login.blocked")))
            throw BlackListException()
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    private fun recordLoginInfo(userId: Long?) {
        val sysUser = SysUser()
        sysUser.userId = userId
        sysUser.loginIp = ipAddr
        sysUser.loginDate = nowDate
        userService.updateUserProfile(sysUser)
    }
}
